/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *    GraphVisualizer.java
 *    Copyright (C) 2003 Ashraf M. Kibriya
 *
 */
package pATT.visualize.graphVisualize;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.table.AbstractTableModel;

import pATT.core.FastVector;
import pATT.core.visual.ExtensionFileFilter;
import pATT.visualize.treeVisualize.PrintablePanel;

/**
 * This class displays the graph we want to visualize. It should
 * be sufficient to use only this class in weka.gui.graphvisulizer
 * package to visualize a graph. The description of a graph should
 * be provided as a string argument using readBIF or readDOT method
 * in either XMLBIF03 or DOT format. Alternatively, an InputStream
 * in XMLBIF03 can also be provided to another variation of readBIF.
 * It would be necessary in case input is in DOT format to call the
 * layoutGraph() method to display the graph correctly after the call
 * to readDOT. It is also necessary to do so if readBIF is called and
 * the graph description doesn't have x y positions for nodes.
 * <p> The graph's data is held in two FastVectors, nodes are stored as
 * objects of GraphNode class and edges as objects of GraphEdge class.
 * <p> The graph is displayed by positioning and drawing each node
 * according to its x y position and then drawing all the edges coming
 * out of it give by its edges[][] array, the arrow heads are ofcourse
 * marked in the opposite(ie original direction) or both directions if
 * the edge is reversed or is in both directions. The graph is centered
 * if it is smaller than it's display area. The edges are drawn from the
 * bottom of the current node to the top of the node given by edges[][]
 * array in GraphNode class, to avoid edges crossing over other nodes.
 * This might need to be changed if another layout engine is added or
 * the current Hierarchical engine is updated to avoid such crossings
 * over nodes.
 *
 * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
 * @version $Revision: 1.3.2.2 $
 */
@SuppressWarnings("static-access")
public class GraphVisualizer extends JPanel implements GraphConstants,
LayoutCompleteEventListener  {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	/** Vector containing nodes */
	protected FastVector m_nodes=new FastVector();
	/** Vector containing edges */
	protected FastVector m_edges=new FastVector();
	/** The current LayoutEngine  */
	protected LayoutEngine m_le;
	/** Panel actually displaying the graph */
	protected GraphPanel m_gp;
	/** String containing graph's name */
	protected String graphID;
	
	/**
	 * Save button to save the current graph in DOT or XMLBIF format.
	 * The graph should be layed out again to get the original form
	 * if reloaded from command line, as the formats do not allow
	 * saving specific information for a properly layed out graph.
	 */
	protected JButton m_jBtSave;
	
	/** path for icons */
	private final String ICONPATH = "/pATT/visualize.graphVisualize";//"weka/gui/graphvisualizer/icons/";
	
	private FontMetrics fm = this.getFontMetrics( this.getFont() );
	private double scale = 1;   //current zoom
	private int nodeHeight = 2*fm.getHeight(), nodeWidth = 24;
	private int paddedNodeWidth = 24+8;
	/** TextField for node's width */
	private final JTextField jTfNodeWidth = new JTextField(3);
	/** TextField for nodes height */
	private final JTextField jTfNodeHeight = new JTextField(3);
	/** Button for laying out the graph again, necessary after changing node's
	 * size or some other property of the layout engine
	 */
	private final JButton jBtLayout;
	/** used for setting appropriate node size */
	private int maxStringWidth=0;
	/** used when using zoomIn and zoomOut buttons */
	private int [] zoomPercents = { 10, 25, 50, 75, 100, 125, 150, 175, 200, 225,
			250, 275, 300, 350, 400, 450, 500, 550, 600, 650, 700, 800, 900, 999 };
	/** this contains the m_gp GraphPanel */
	JScrollPane m_js;
	
	/**
	 * Constructor<br>
	 * Sets up the gui and initializes all the other previously
	 * uninitialized variables.
	 */
	public GraphVisualizer() {
		m_gp = new GraphPanel();
		m_js = new JScrollPane(m_gp);
		
		//creating a new layout engine and adding this class as its listener
		// to receive layoutComplete events
		m_le=new HierarchicalBCEngine(m_nodes, m_edges, 
				paddedNodeWidth, nodeHeight);
		m_le.addLayoutCompleteEventListener(this);
		
		m_jBtSave = new JButton();
		java.net.URL tempURL = ClassLoader.getSystemResource(ICONPATH+"save.gif");
		if(tempURL!=null)
			m_jBtSave.setIcon(new ImageIcon(tempURL) );
		else
			System.err.println(ICONPATH+
			"save.gif not found for weka.gui.graphvisualizer.Graph");
		m_jBtSave.setToolTipText("Save Graph");
		m_jBtSave.addActionListener( new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				JFileChooser fc = new JFileChooser(System.getProperty("user.dir"));
				ExtensionFileFilter ef1 = new ExtensionFileFilter(".dot", "DOT files");
				ExtensionFileFilter ef2 = new ExtensionFileFilter(".xml",
				"XML BIF files");
				fc.addChoosableFileFilter(ef1);
				fc.addChoosableFileFilter(ef2);
				fc.setDialogTitle("Save Graph As");
				int rval = fc.showSaveDialog(GraphVisualizer.this);
				
				if (rval == JFileChooser.APPROVE_OPTION) {
					//System.out.println("Saving to file \""+
					//                   f.getAbsoluteFile().toString()+"\"");
					if(fc.getFileFilter()==ef2) {
						String filename = fc.getSelectedFile().toString();
						if(!filename.endsWith(".xml"))
							filename = filename.concat(".xml");
						BIFParser.writeXMLBIF03(filename, graphID, m_nodes, m_edges);
					}
					else {
						String filename = fc.getSelectedFile().toString();
						if(!filename.endsWith(".dot"))
							filename = filename.concat(".dot");
						DotParser.writeDOT(filename, graphID, m_nodes, m_edges);
					}
				}
			}
		});
		
		final JButton jBtZoomIn = new JButton();
		tempURL = ClassLoader.getSystemResource(ICONPATH+"zoomin.gif");
		if(tempURL!=null)
			jBtZoomIn.setIcon(new ImageIcon(tempURL) );
		else
			System.err.println(ICONPATH+
			"zoomin.gif not found for weka.gui.graphvisualizer.Graph");
		jBtZoomIn.setToolTipText("Zoom In");
		
		final JButton jBtZoomOut = new JButton();
		tempURL = ClassLoader.getSystemResource(ICONPATH+"zoomout.gif");
		if(tempURL!=null)
			jBtZoomOut.setIcon(new ImageIcon(tempURL) );
		else
			System.err.println(ICONPATH+
			"zoomout.gif not found for weka.gui.graphvisualizer.Graph");
		jBtZoomOut.setToolTipText("Zoom Out");
		
		final JTextField jTfZoom = new JTextField("100%");
		jTfZoom.setMinimumSize( jTfZoom.getPreferredSize() );
		jTfZoom.setHorizontalAlignment(JTextField.CENTER);
		jTfZoom.setToolTipText("Zoom");
		
		jTfZoom.addActionListener( new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				JTextField jt = (JTextField)ae.getSource();
				try {
					int i=-1;
					i = jt.getText().indexOf('%');
					if(i==-1)
						i = Integer.parseInt(jt.getText());
					else
						i = Integer.parseInt(jt.getText().substring(0,i));
					
					if(i<=999)
						scale = i/100D;
					
					jt.setText((int)(scale*100)+"%");
					
					if(scale>0.1){
						if(!jBtZoomOut.isEnabled())
							jBtZoomOut.setEnabled(true);
					}
					else
						jBtZoomOut.setEnabled(false);
					if(scale<9.99) {
						if(!jBtZoomIn.isEnabled())
							jBtZoomIn.setEnabled(true);
					}
					else
						jBtZoomIn.setEnabled(false);
					
					setAppropriateSize();
					//m_gp.clearBuffer();
					m_gp.repaint();
					m_gp.invalidate();
					m_js.revalidate();
				} catch(NumberFormatException ne) {
					JOptionPane.showMessageDialog(GraphVisualizer.this.getParent(),
							"Invalid integer entered for zoom.",
							"Error",
							JOptionPane.ERROR_MESSAGE);
					jt.setText((scale*100)+"%");
				}
			}
		});
		
		
		jBtZoomIn.addActionListener( new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				int i=0, s = (int)(scale*100);
				if(s<300)
					i = s/25;
				else if(s<700)
					i = 6 +  s/50;
				else
					i = 13 +s/100;
				
				if(s>=999) {
					JButton b = (JButton)ae.getSource();
					b.setEnabled(false);
					return;
				}
				else if(s>=10){
					if(i>=22) {
						JButton b = (JButton)ae.getSource();
						b.setEnabled(false);
					}
					if(s==10 && !jBtZoomOut.isEnabled())
						jBtZoomOut.setEnabled(true);
					//System.out.println("i: "+i+"Zoom is: "+zoomPercents[i+1]);
					jTfZoom.setText(zoomPercents[i+1]+"%");
					scale = zoomPercents[i+1]/100D;
				}
				else {
					if(!jBtZoomOut.isEnabled())
						jBtZoomOut.setEnabled(true);
					//System.out.println("i: "+i+"Zoom is: "+zoomPercents[0]);
					jTfZoom.setText(zoomPercents[0]+"%");
					scale = zoomPercents[0]/100D;
				}
				setAppropriateSize();
				m_gp.repaint();
				m_gp.invalidate();
				m_js.revalidate();
			}
		});
		
		
		jBtZoomOut.addActionListener( new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				int i=0, s = (int)(scale*100);
				if(s<300)
					i = (int) Math.ceil(s/25D);
				else if(s<700)
					i = 6 +  (int) Math.ceil(s/50D);
				else
					i = 13 + (int) Math.ceil(s/100D);
				
				if(s<=10) {
					JButton b = (JButton)ae.getSource();
					b.setEnabled(false);
				}
				else if(s<999) {
					if(i<=1) {
						JButton b = (JButton)ae.getSource();
						b.setEnabled(false);
					}
					//System.out.println("i: "+i+"Zoom is: "+zoomPercents[i-1]);
					jTfZoom.setText(zoomPercents[i-1]+"%");
					scale = zoomPercents[i-1]/100D;
				}
				else{
					if(!jBtZoomIn.isEnabled())
						jBtZoomIn.setEnabled(true);
					//System.out.println("i: "+i+"Zoom is: "+zoomPercents[22]);
					jTfZoom.setText(zoomPercents[22]+"%");
					scale = zoomPercents[22]/100D;
				}
				setAppropriateSize();
				m_gp.repaint();
				m_gp.invalidate();
				m_js.revalidate();
			}
		});
		
		
		//This button pops out the extra controls
		JButton jBtExtraControls = new JButton();
		tempURL = ClassLoader.getSystemResource(ICONPATH+"extra.gif");
		if(tempURL!=null)
			jBtExtraControls.setIcon(new ImageIcon(tempURL) );
		else
			System.err.println(ICONPATH+
			"extra.gif not found for weka.gui.graphvisualizer.Graph");
		jBtExtraControls.setToolTipText("Show/Hide extra controls");
		
		
		final JCheckBox jCbCustomNodeSize = new JCheckBox("Custom Node Size");
		final JLabel jLbNodeWidth = new JLabel("Width");
		final JLabel jLbNodeHeight = new JLabel("Height");
		
		jTfNodeWidth.setHorizontalAlignment(JTextField.CENTER);
		jTfNodeWidth.setText(""+nodeWidth);
		jTfNodeHeight.setHorizontalAlignment(JTextField.CENTER);
		jTfNodeHeight.setText(""+nodeHeight);
		jLbNodeWidth.setEnabled(false);
		jTfNodeWidth.setEnabled(false);
		jLbNodeHeight.setEnabled(false);
		jTfNodeHeight.setEnabled(false);
		
		jCbCustomNodeSize.addActionListener( new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				if( ((JCheckBox)ae.getSource()).isSelected() ) {
					jLbNodeWidth.setEnabled(true);
					jTfNodeWidth.setEnabled(true);
					jLbNodeHeight.setEnabled(true);
					jTfNodeHeight.setEnabled(true);
				}
				else {
					jLbNodeWidth.setEnabled(false);
					jTfNodeWidth.setEnabled(false);
					jLbNodeHeight.setEnabled(false);
					jTfNodeHeight.setEnabled(false);
					setAppropriateNodeSize();
				}
			}
		});
		
		
		jBtLayout  = new JButton("Layout Graph");
		jBtLayout.addActionListener( new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				int tmpW, tmpH;
				
				if(jCbCustomNodeSize.isSelected()) {
					try{ tmpW = Integer.parseInt(jTfNodeWidth.getText()); }
					catch(NumberFormatException ne) {
						JOptionPane.showMessageDialog(GraphVisualizer.this.getParent(),
								"Invalid integer entered for node width.",
								"Error",
								JOptionPane.ERROR_MESSAGE);
						tmpW = nodeWidth;
						jTfNodeWidth.setText(""+nodeWidth);
						
					}
					try{ tmpH = Integer.parseInt(jTfNodeHeight.getText()); }
					catch(NumberFormatException ne) {
						JOptionPane.showMessageDialog(GraphVisualizer.this.getParent(),
								"Invalid integer entered for node height.",
								"Error",
								JOptionPane.ERROR_MESSAGE);
						tmpH = nodeHeight;
						jTfNodeWidth.setText(""+nodeHeight);
					}
					
					if(tmpW!=nodeWidth || tmpH!=nodeHeight) {
						nodeWidth = tmpW; paddedNodeWidth = nodeWidth+8; nodeHeight = tmpH;
					}
				}
				JButton bt = (JButton)ae.getSource();
				bt.setEnabled(false);
				m_le.setNodeSize(paddedNodeWidth, nodeHeight);
				m_le.layoutGraph();
			}
		});
		
		
		GridBagConstraints gbc = new GridBagConstraints();
		
		final JPanel p = new JPanel(new GridBagLayout());
		gbc.gridwidth = gbc.REMAINDER;
		gbc.anchor = gbc.NORTHWEST;
		gbc.fill = gbc.NONE;
		p.add( m_le.getControlPanel(), gbc);
		gbc.gridwidth = 1;
		gbc.insets = new Insets(8,0,0,0);
		gbc.anchor = gbc.NORTHWEST;
		gbc.gridwidth = gbc.REMAINDER;
		
		p.add( jCbCustomNodeSize, gbc );
		gbc.insets = new Insets(0,0,0,0);
		gbc.gridwidth = gbc.REMAINDER;
		Container c = new Container();
		c.setLayout( new GridBagLayout() );
		gbc.gridwidth = gbc.RELATIVE;
		c.add(jLbNodeWidth, gbc);
		gbc.gridwidth = gbc.REMAINDER;
		c.add(jTfNodeWidth, gbc);
		gbc.gridwidth = gbc.RELATIVE;
		c.add(jLbNodeHeight, gbc);
		gbc.gridwidth = gbc.REMAINDER;
		c.add(jTfNodeHeight, gbc);
		gbc.fill = gbc.HORIZONTAL;
		p.add( c, gbc );
		
		gbc.anchor = gbc.NORTHWEST;
		gbc.insets = new Insets(8,0,0,0);
		gbc.fill = gbc.HORIZONTAL;
		p.add( jBtLayout, gbc );
		gbc.fill = gbc.NONE;
		p.setBorder(BorderFactory.createCompoundBorder(
				BorderFactory.createTitledBorder("ExtraControls"),
				BorderFactory.createEmptyBorder(4,4,4,4)
		) );
		p.setPreferredSize( new Dimension(0, 0) );
		
		final JToolBar jTbTools = new JToolBar();
		jTbTools.setFloatable(false);
		jTbTools.setLayout( new GridBagLayout() );
		gbc.anchor = gbc.NORTHWEST;
		gbc.gridwidth = gbc.REMAINDER;
		gbc.insets = new Insets(0,0,0,0);
		jTbTools.add(p,gbc);
		gbc.gridwidth = 1;
		jTbTools.add(m_jBtSave, gbc);
		jTbTools.addSeparator(new Dimension(2,2));
		jTbTools.add(jBtZoomIn, gbc);
		
		gbc.fill = gbc.VERTICAL;
		gbc.weighty = 1;
		JPanel p2 = new JPanel(new BorderLayout());
		p2.setPreferredSize( jTfZoom.getPreferredSize() );
		p2.setMinimumSize( jTfZoom.getPreferredSize() );
		p2.add(jTfZoom, BorderLayout.CENTER);
		jTbTools.add(p2, gbc);
		gbc.weighty =0;
		gbc.fill = gbc.NONE;
		
		jTbTools.add(jBtZoomOut, gbc);
		jTbTools.addSeparator(new Dimension(2,2));
		jTbTools.add(jBtExtraControls, gbc);
		jTbTools.addSeparator(new Dimension(4,2));
		gbc.weightx = 1;
		gbc.fill = gbc.BOTH;
		jTbTools.add(m_le.getProgressBar(), gbc);
		
		jBtExtraControls.addActionListener( new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				Dimension d = p.getPreferredSize();
				if(d.width==0 || d.height==0) {
					LayoutManager lm = p.getLayout();
					Dimension d2 = lm.preferredLayoutSize(p);
					p.setPreferredSize(d2); jTbTools.revalidate();
					/*
					 // this piece of code adds in an animation
					  // for popping out the extra controls panel
					   Thread th = new Thread() {
					   int h = 0, w = 0;
					   LayoutManager lm = p.getLayout();
					   Dimension d2 = lm.preferredLayoutSize(p);
					   
					   int tow = (int)d2.getWidth(), toh = (int)d2.getHeight();
					   //toh = (int)d2.getHeight();
					    //tow = (int)d2.getWidth();
					     
					     public void run() {
					     while(h<toh || w<tow) {
					     if((h+10)<toh)
					     h += 10;
					     else if(h<toh)
					     h = toh;
					     if((w+10)<tow)
					     w += 10;
					     else if(w<tow)
					     w = tow;
					     p.setPreferredSize(new Dimension(w, h));
					     //p.invalidate();
					      jTbTools.revalidate();
					      //paint(Temp4.this.getGraphics());
					       try {this.sleep(30);}
					       catch(InterruptedException ie) {ie.printStackTrace(); break;}
					       }
					       p.setPreferredSize(new Dimension(tow,toh)); jTbTools.revalidate();
					       }
					       };
					       th.start();
					       */
				}
				else {
					p.setPreferredSize( new Dimension(0,0) );
					jTbTools.revalidate();
					/*
					 Thread th = new Thread() {
					 int h = p.getHeight(), w = p.getWidth();
					 LayoutManager lm = p.getLayout();
					 int tow = 0, toh = 0;
					 
					 public void run() {
					 while(h>toh || w>tow) {
					 if((h-10)>toh)
					 h -= 10;
					 else if(h>toh)
					 h = toh;
					 if((w-10)>tow)
					 w -= 10;
					 else if(w>tow)
					 w = tow;
					 
					 p.setPreferredSize(new Dimension(w, h));
					 //p.invalidate();
					  jTbTools.revalidate();
					  //paint(Temp4.this.getGraphics());
					   try {this.sleep(30);}
					   catch(InterruptedException ie) {ie.printStackTrace(); break;}
					   }
					   p.setPreferredSize(new Dimension(tow,toh)); jTbTools.revalidate();
					   }
					   };
					   th.start();
					   */
				}
			}
		});
		this.setLayout( new BorderLayout() );
		this.add(jTbTools, BorderLayout.NORTH);
		this.add(m_js, BorderLayout.CENTER);
	}
	
	
	/**
	 * This method sets the node size that is appropriate
	 * considering the maximum label size that is present.
	 * It is used internally when custom node size checkbox
	 * is unchecked.
	 */
	protected void setAppropriateNodeSize() {
		int strWidth;
		if(maxStringWidth==0)
			for(int i=0; i<m_nodes.size(); i++) {
				strWidth = fm.stringWidth(((GraphNode)m_nodes.elementAt(i)).lbl);
				if(strWidth>maxStringWidth)
					maxStringWidth=strWidth;
			}
		nodeWidth = maxStringWidth+4;
		paddedNodeWidth = nodeWidth+8;
		jTfNodeWidth.setText(""+nodeWidth);
		
		nodeHeight = 2*fm.getHeight();
		jTfNodeHeight.setText(""+nodeHeight);
	}
	
	/**
	 * Sets the preferred size for m_gp GraphPanel to the
	 * minimum size that is neccessary to display the graph.
	 */
	protected void setAppropriateSize() {
		int maxX=0, maxY=0;
		
		m_gp.setScale(scale, scale);
		
		for(int i=0; i<m_nodes.size(); i++) {
			GraphNode n = (GraphNode)m_nodes.elementAt(i);
			if(maxX<n.x)
				maxX=n.x;
			if(maxY<n.y)
				maxY=n.y;
		}
		//System.out.println("Scale: "+scale+" paddedWidth: "+paddedNodeWidth+
		//                   " nodeHeight: "+nodeHeight+"\nmaxX: "+maxX+" maxY: "+
		//                   maxY+" final: "+(int)((maxX+paddedNodeWidth+2)*scale)+
		//                   ","+(int)((maxY+nodeHeight+2)*scale) );
		m_gp.setPreferredSize(new Dimension((int)((maxX+paddedNodeWidth+2)*scale),
				(int)((maxY+nodeHeight+2)*scale)));
		//System.out.println("Size set to "+this.getPreferredSize());
	}
	
	
	/**
	 * This method is an implementation for LayoutCompleteEventListener
	 * class. It sets the size appropriate for m_gp GraphPanel and
	 * and revalidates it's container JScrollPane once a
	 * LayoutCompleteEvent is received from the LayoutEngine.
	 */
	public void layoutCompleted(LayoutCompleteEvent le) {
		setAppropriateSize();
		//m_gp.clearBuffer();
		m_gp.invalidate();
		m_js.revalidate();
		m_gp.repaint();
		jBtLayout.setEnabled(true);
	}
	
	
	/**
	 * This method lays out the graph by calling the
	 * LayoutEngine's layoutGraph() method. This method
	 * should be called to display the graph nicely, unless
	 * the input XMLBIF03 already contains some layout
	 * information (ie the x,y positions of nodes.
	 */
	public void layoutGraph() {
		if(m_le!=null)
			m_le.layoutGraph();
		
	}
	
	/*********************************************************
	 *
	 *  BIF reader<br>
	 *  Reads a graph description in XMLBIF03 from a string
	 *
	 *********************************************************
	 */
	public void readBIF(String instring) throws BIFFormatException {
		BIFParser bp = new BIFParser(instring, m_nodes, m_edges);
		try {
			graphID = bp.parse();
		} catch(BIFFormatException bf) {
			System.out.println("BIF format error");
			bf.printStackTrace();
		}
		catch(Exception ex) { ex.printStackTrace(); return; }
		
		setAppropriateNodeSize();
		if(m_le!=null) {
			m_le.setNodeSize(paddedNodeWidth, nodeHeight);
		}
	} //end readBIF1
	
	/**
	 *
	 *  BIF reader<br>
	 *  Reads a graph description in XMLBIF03 from an InputStrem
	 *
	 *
	 */
	public void readBIF(InputStream instream) throws BIFFormatException {
		BIFParser bp = new BIFParser(instream, m_nodes, m_edges);
		try {
			graphID = bp.parse();
		} catch(BIFFormatException bf) {
			System.out.println("BIF format error");
			bf.printStackTrace();
		}
		catch(Exception ex) { ex.printStackTrace(); return; }
		
		setAppropriateNodeSize();
		if(m_le!=null) {
			m_le.setNodeSize(paddedNodeWidth, nodeHeight);
		}
		setAppropriateSize();
	} //end readBIF2
	
	
	/*********************************************************
	 *
	 *  Dot reader<br>
	 *  Reads a graph description in DOT format from a string
	 *
	 *********************************************************
	 */
	public void readDOT(Reader input) {
		DotParser dp = new DotParser(input, m_nodes, m_edges);
		graphID = dp.parse();
		
		setAppropriateNodeSize();
		if(m_le!=null) {
			m_le.setNodeSize(paddedNodeWidth, nodeHeight);
			jBtLayout.setEnabled(false);
			layoutGraph();
		}
	}
	
	
	/**
	 * The panel which contains the actual  graph.
	 *
	 */
	private class GraphPanel extends PrintablePanel {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		
		//Image buf;
		public GraphPanel() {
			super();
			this.addMouseListener( new GraphVisualizerMouseListener() );
			this.addMouseMotionListener( new GraphVisualizerMouseMotionListener() );
			this.setToolTipText("");
		}
		
		public String getToolTipText(MouseEvent me) {
			int x, y, nx, ny;
			Rectangle r;
			GraphNode n;
			Dimension d = m_gp.getPreferredSize();
			//System.out.println("Preferred Size: "+this.getPreferredSize()+
			//                   " Actual Size: "+this.getSize());
			x=y=nx=ny=0;
			
			if(d.width < m_gp.getWidth())
				nx = (int)((nx + m_gp.getWidth()/2 - d.width/2)/scale);
			if(d.height < m_gp.getHeight())
				ny = (int)((ny + m_gp.getHeight()/2 - d.height/2)/scale);
			
			r = new Rectangle(0, 0, 
					(int)(paddedNodeWidth*scale), (int)(nodeHeight*scale));
			x += me.getX(); y += me.getY();
			
			int i;
			for(i=0; i<m_nodes.size(); i++) {
				n = (GraphNode) m_nodes.elementAt(i);
				if(n.nodeType!=NORMAL)
					return null;
				r.x = (int)((nx+n.x)*scale); r.y = (int)((ny+n.y)*scale);
				if(r.contains(x,y)) {
					if(n.probs==null)
						return n.lbl;
					else
						return n.lbl+" (click to view the probability dist. table)";
				}
			}
			return null;
		}
		
		
		public void paintComponent(Graphics gr) {
			Graphics2D g = (Graphics2D)gr;
			RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);
			rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
			g.setRenderingHints(rh);
			g.scale(scale, scale);
			Rectangle r = g.getClipBounds();
			g.clearRect(r.x,r.y,r.width,r.height);
			//g.setColor(this.getBackground());
			//g.fillRect(0, 0, width+5, height+5);
			int x=0, y=0;
			Dimension d = this.getPreferredSize();
			//System.out.println("Preferred Size: "+this.getPreferredSize()+
			//                   " Actual Size: "+this.getSize());
			
			//initializing x & y to display the graph in the middle
			//if the display area is larger than the graph
			if(d.width < this.getWidth())
				x = (int)((x + this.getWidth()/2 - d.width/2)/scale);
			if(d.height < this.getHeight())
				y = (int)((y + this.getHeight()/2 - d.height/2)/scale);
			
			for(int index=0; index<m_nodes.size(); index++) {
				GraphNode n = (GraphNode) m_nodes.elementAt(index);
				if( n.nodeType==NORMAL) {
					g.setColor( this.getBackground().darker().darker() );
					g.fillOval(x+n.x+paddedNodeWidth-nodeWidth-
							(paddedNodeWidth-nodeWidth)/2,
							y+n.y,
							nodeWidth, nodeHeight);
					
					g.setColor(Color.white);
					//g.setColor(Color.black);
					//System.out.println("drawing "+
					//                   ((GraphNode)m_nodes.elementAt(index)).ID+
					//                   " at "+" x: "+ (x+n.x+paddedNodeWidth/2-
					//      fm.stringWidth( ((GraphNode)m_nodes.elementAt(index)).ID )/2)+
					//		       " y: "+(y+n.y+nodeHeight/2+fm.getHeight()/2-2) );
					
					
					//Draw the node's label if it can fit inside the node's current
					// width otherwise display its ID or otherwise just display its
					// idx in the FastVector (to distinguish it from others)
					// if any can fit in node's current width
					if(fm.stringWidth(n.lbl)<=nodeWidth)
						g.drawString( n.lbl,
								x+n.x+paddedNodeWidth/2
								-fm.stringWidth( n.lbl )/2,
								y+n.y+nodeHeight/2+fm.getHeight()/2-2 );
					else if(fm.stringWidth(n.ID)<=nodeWidth)
						g.drawString( n.ID,
								x+n.x+paddedNodeWidth/2
								-fm.stringWidth( n.ID )/2,
								y+n.y+nodeHeight/2+fm.getHeight()/2-2 );
					else if(fm.stringWidth( Integer.toString(index) )<=nodeWidth)
						g.drawString( Integer.toString(index),
								x+n.x+paddedNodeWidth/2
								-fm.stringWidth( Integer.toString(index) )/2,
								y+n.y+nodeHeight/2+fm.getHeight()/2-2 );
					
					g.setColor(Color.black);
				}
				else {
					//g.draw( new java.awt.geom.QuadCurve2D.Double(n.x+paddedNodeWidth/2, 
					//                                             n.y,
					//				n.x+paddedNodeWidth-nodeSize
					//                                   -(paddedNodeWidth-nodeSize)/2,
					//                                  n.y+nodeHeight/2,
					//                           n.x+paddedNodeWidth/2, n.y+nodeHeight) );
					g.drawLine(x+n.x+paddedNodeWidth/2, y+n.y, 
							x+n.x+paddedNodeWidth/2, y+n.y+nodeHeight);
					
				}
				
				GraphNode n2;
				int x1, y1, x2, y2;
				//System.out.println("Drawing edges of "+n.lbl);
				
				//Drawing all the edges coming out from the node,
				//including reversed and double ones
				if(n.edges!=null)
					for(int k=0; k<n.edges.length; k++) {
						if(n.edges[k][1]>0) {
							n2 = (GraphNode) m_nodes.elementAt(n.edges[k][0]); //m_nodes.elementAt(k);
							//System.out.println("  -->to "+n2.lbl);
							x1=n.x+paddedNodeWidth/2; y1=n.y+nodeHeight;
							x2=n2.x+paddedNodeWidth/2; y2=n2.y;
							g.drawLine(x+x1, y+y1, x+x2, y+y2);
							if(n.edges[k][1]==DIRECTED) {
								if(n2.nodeType==n2.NORMAL)
									drawArrow(g, x+x1, y+y1, x+x2, y+y2);
							}
							else if(n.edges[k][1]==REVERSED) {
								if(n.nodeType==NORMAL)
									drawArrow(g, x+x2, y+y2, x+x1, y+y1);
							}
							else if(n.edges[k][1]==DOUBLE) {
								if(n.nodeType==NORMAL)
									drawArrow(g, x+x2, y+y2, x+x1, y+y1);
								if(n2.nodeType==NORMAL)
									drawArrow(g, x+x1, y+y1, x+x2, y+y2);
							}
						}
					}
			}
		}
		
		/**
		 * This method draws an arrow on a line from (x1,y1)
		 * to (x2,y2). The arrow head is seated on (x2,y2) and
		 * is in the direction of the line.
		 * If the arrow is needed to be drawn in the opposite
		 * direction then simply swap the order of (x1, y1)
		 * and (x2, y2) when calling this function.
		 */
		protected void drawArrow(Graphics g, int x1, int y1, int x2, int y2) {
			
			if(x1==x2) {
				if(y1<y2) {
					g.drawLine(x2, y2, x2+4, y2-8);
					g.drawLine(x2, y2, x2-4, y2-8);
				}
				else {
					g.drawLine(x2, y2, x2+4, y2+8);
					g.drawLine(x2, y2, x2-4, y2+8);
				}
			}
			else {
				//theta=line's angle from base, beta=angle of arrow's side from line
				double hyp=0, base=0, perp=0, theta, beta;
				int x3=0, y3=0;
				
				if(x2<x1) {
					base = x1-x2; hyp = Math.sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) );
					theta = Math.acos( base/hyp );
				}
				else { //x1>x2 as we already checked x1==x2 before
					base = x1-x2; hyp = Math.sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) );
					theta = Math.acos( base/hyp );
				}
				beta = 30*Math.PI/180;
				//System.out.println("Original base "+base+" perp "+perp+" hyp "+hyp+
				//                   "\ntheta "+theta+" beta "+beta);
				
				hyp = 8;
				base = Math.cos(theta-beta)*hyp;
				perp = Math.sin(theta-beta)*hyp;
				
				x3 = (int)(x2+base);
				if(y1<y2)
					y3 = (int)(y2-perp);
				else
					y3 = (int)(y2+perp);
				
				//System.out.println("Drawing 1 from "+x2+","+y2+" to "+x3+","+y3+
				//                   " x1,y1 is "+x1+","+y1+" base "+base+
				//		     " perp "+perp+" cos(theta-beta) "+
				//                   Math.cos(theta-beta));
				g.drawLine(x2, y2, x3, y3);
				
				base = Math.cos(theta+beta)*hyp;
				perp = Math.sin(theta+beta)*hyp;
				
				x3 = (int)(x2+base);
				if(y1<y2)
					y3 = (int)(y2-perp);
				else
					y3 = (int)(y2+perp);
				//System.out.println("Drawing 2 from "+x2+","+y2+" to "+x3+","+y3+
				//                   " x1,y1 is "+x1+","+y1+" base "+base+
				//		     " perp "+perp);
				g.drawLine(x2, y2, x3, y3);
			}
		}
		
		/**
		 * This method highlights a given node and all its children
		 * and the edges coming out of it.
		 */
		@SuppressWarnings("unchecked")
		public void highLight(GraphNode n) {
			Graphics2D g = (Graphics2D) this.getGraphics();
			RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);
			rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
			g.setRenderingHints(rh);
			g.setPaintMode();
			g.scale(scale, scale);
			int x=0, y=0;
			Dimension d = this.getPreferredSize();
			//System.out.println("Preferred Size: "+this.getPreferredSize()+
			//                   " Actual Size: "+this.getSize());
			
			//initializing x & y to display the graph in the middle
			//if the display area is larger than the graph
			if(d.width < this.getWidth())
				x = (int)((x + this.getWidth()/2 - d.width/2)/scale);
			if(d.height < this.getHeight())
				y = (int)((y + this.getHeight()/2 - d.height/2)/scale);
			
			//if the node is of type NORMAL only then highlight
			if(n.nodeType==NORMAL) {
				
				g.setXORMode(Color.green); //g.setColor(Color.green);
				
				g.fillOval(x+n.x+paddedNodeWidth-nodeWidth-
						(paddedNodeWidth-nodeWidth)/2,
						y+n.y, nodeWidth, nodeHeight);
				g.setXORMode(Color.red);
				
				//Draw the node's label if it can fit inside the node's current
				// width otherwise display its ID or otherwise just display its
				// idx in the FastVector (to distinguish it from others)
				// if any can fit in node's current width
				if(fm.stringWidth(n.lbl)<=nodeWidth)
					g.drawString( n.lbl,
							x+n.x+paddedNodeWidth/2
							-fm.stringWidth( n.lbl )/2,
							y+n.y+nodeHeight/2+fm.getHeight()/2-2 );
				else if(fm.stringWidth(n.ID)<=nodeWidth)
					g.drawString( n.ID,
							x+n.x+paddedNodeWidth/2
							-fm.stringWidth( n.ID )/2,
							y+n.y+nodeHeight/2+fm.getHeight()/2-2 );
				else if( fm.stringWidth( Integer.toString(m_nodes.indexOf(n)) ) <=
					nodeWidth )
					g.drawString( Integer.toString(m_nodes.indexOf(n)),
							x+n.x+paddedNodeWidth/2
							-fm.stringWidth( Integer.toString(m_nodes.indexOf(n)) )/2,
							y+n.y+nodeHeight/2+fm.getHeight()/2-2 );
				
				g.setXORMode(Color.green);
				
				
				GraphNode n2;
				int x1, y1, x2, y2;
				//System.out.println("Drawing edges of "+n.lbl);
				if(n.edges!=null)
					//Drawing all the edges from and upward ones coming to the node
					for(int k=0; k<n.edges.length; k++) {
						if(n.edges[k][1]==DIRECTED || n.edges[k][1]==DOUBLE) {
							n2 = (GraphNode) m_nodes.elementAt(n.edges[k][0]); //m_nodes.elementAt(k);
							//System.out.println("  -->to "+n2.lbl);
							x1=n.x+paddedNodeWidth/2; y1=n.y+nodeHeight;
							x2=n2.x+paddedNodeWidth/2; y2=n2.y;
							g.drawLine(x+x1, y+y1, x+x2, y+y2);
							if(n.edges[k][1]==DIRECTED) {
								if(n2.nodeType==n2.NORMAL) //!n2.dummy)
									drawArrow(g, x+x1, y+y1, x+x2, y+y2);
							}
							else if(n.edges[k][1]==DOUBLE) {
								if(n.nodeType==NORMAL) //!n.dummy)
									drawArrow(g, x+x2, y+y2, x+x1, y+y1);
								if(n2.nodeType==NORMAL) //!n2.dummy)
									drawArrow(g, x+x1, y+y1, x+x2, y+y2);
							}
							if(n2.nodeType==NORMAL)
								g.fillOval(x+n2.x+paddedNodeWidth-nodeWidth-
										(paddedNodeWidth-nodeWidth)/2,
										y+n2.y, nodeWidth, nodeHeight);
							
							//If n2 is not of NORMAL type
							// then carry on drawing all the edges and add all the
							// dummy nodes encountered in a Vector until no
							// more dummy nodes are found and all the child nodes(node n2)
							// are of type normal
							java.util.Vector t = new java.util.Vector();
							while(n2.nodeType!=NORMAL || t.size()>0) { //n2.dummy==true) {
								//System.out.println("in while processing "+n2.ID);
								if(t.size()>0)
								{ n2 = (GraphNode)t.elementAt(0);
								t.removeElementAt(0); }
								if(n2.nodeType!=NORMAL) {
									g.drawLine(x+n2.x+paddedNodeWidth/2, y+n2.y,
											x+n2.x+paddedNodeWidth/2, y+n2.y+nodeHeight);
									x1=n2.x+paddedNodeWidth/2; y1=n2.y+nodeHeight;
									//System.out.println("Drawing from "+n2.lbl);
									for(int m=0; m<n2.edges.length; m++) {
										//System.out.println(" to "+n2.lbl+", "+
										//                   graphMatrix[tmpIndex][m]);
										if(n2.edges[m][1]>0) {
											GraphNode n3 =
												(GraphNode) m_nodes.elementAt(n2.edges[m][0]); //m_nodes.elementAt(m);
											g.drawLine(x+x1, y+y1, x+n3.x+paddedNodeWidth/2, y+n3.y);
											
											if(n3.nodeType==NORMAL){ //!n2.dummy)
												g.fillOval(x+n3.x+paddedNodeWidth-nodeWidth-
														(paddedNodeWidth-nodeWidth)/2,
														y+n3.y, nodeWidth, nodeHeight);
												drawArrow(g, x+x1, y+y1,
														x+n3.x+paddedNodeWidth/2, y+n3.y);
											}
											//if(n3.nodeType!=n3.NORMAL)
											t.addElement(n3);
											//break;
										}
									}
								}
							}
						}
						else if(n.edges[k][1]==-REVERSED || n.edges[k][1]==-DOUBLE) {
							//Drawing all the reversed and double edges which are going
							//upwards in the drawing.
							n2 = (GraphNode) m_nodes.elementAt(n.edges[k][0]); //m_nodes.elementAt(k);
							//System.out.println("  -->to "+n2.lbl);
							x1=n.x+paddedNodeWidth/2; y1=n.y;
							x2=n2.x+paddedNodeWidth/2; y2=n2.y+nodeHeight;
							g.drawLine(x+x1, y+y1, x+x2, y+y2);
							
							if(n.edges[k][1]==-DOUBLE) {
								drawArrow(g, x+x2, y+y2, x+x1, y+y1);
								if(n2.nodeType!=SINGULAR_DUMMY) //!n2.dummy)
									drawArrow(g, x+x1, y+y1, x+x2, y+y2);
							}
							
//							int tmpIndex=k;
							while(n2.nodeType!=NORMAL) { //n2.dummy==true) {
								g.drawLine(x+n2.x+paddedNodeWidth/2,
										y+n2.y+nodeHeight, x+n2.x+paddedNodeWidth/2, y+n2.y);
								x1=n2.x+paddedNodeWidth/2; y1=n2.y;
								for(int m=0; m<n2.edges.length; m++) {
									if(n2.edges[m][1]<0) {
										n2 = (GraphNode) m_nodes.elementAt(n2.edges[m][0]); //m_nodes.elementAt(m);
										g.drawLine(x+x1, y+y1,
												x+n2.x+paddedNodeWidth/2, y+n2.y+nodeHeight);
//										tmpIndex=m;
										if(n2.nodeType!=SINGULAR_DUMMY) //!n2.dummy)
											drawArrow(g, x+x1, y+y1,
													x+n2.x+paddedNodeWidth/2, y+n2.y+nodeHeight);
										break;
									}
								}
							}
						}
					}
			}
		}
	}
	
	
	/**
	 * Table Model for the Table that shows the probability
	 * distribution for a node
	 */
	private class GraphVisualizerTableModel extends AbstractTableModel {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		final String[] columnNames;
		final double[][] data;
		
		
		public GraphVisualizerTableModel(double[][] d, String[] c) {
			data = d;
			columnNames = c;
		}
		
		public int getColumnCount() {
			return columnNames.length;
		}
		
		public int getRowCount() {
			return data.length;
		}
		
		public String getColumnName(int col) {
			return columnNames[col];
		}
		
		public Object getValueAt(int row, int col) {
			return new Double(data[row][col]);
		}
		
		/*
		 * JTable uses this method to determine the default renderer/
		 * editor for each cell.
		 */
		@SuppressWarnings("unchecked")
		public Class getColumnClass(int c) {
			return getValueAt(0, c).getClass();
		}
		
		/*
		 * Implemented this to make sure the table is uneditable.
		 */
		public boolean isCellEditable(int row, int col) {
			return false;
		}
	}
	
	
	
	/**
	 * Listener class for processing mouseClicked
	 */
	private class GraphVisualizerMouseListener extends MouseAdapter {
		int x, y, nx, ny; Rectangle r;
		
		/**
		 * If the mouse is clicked on a node then this method
		 * displays a dialog box with the probability distribution
		 * table for that node IF it exists
		 */
		public void mouseClicked(MouseEvent me) {
			GraphNode n;
			Dimension d = m_gp.getPreferredSize();
			//System.out.println("Preferred Size: "+this.getPreferredSize()+
			//                   " Actual Size: "+this.getSize());
			x=y=nx=ny=0;
			
			if(d.width < m_gp.getWidth())
				nx = (int)((nx + m_gp.getWidth()/2 - d.width/2)/scale);
			if(d.height < m_gp.getHeight())
				ny = (int)((ny + m_gp.getHeight()/2 - d.height/2)/scale);
			
			r=new Rectangle(0, 0, 
					(int)(paddedNodeWidth*scale), (int)(nodeHeight*scale));
			x += me.getX(); y += me.getY();
			
			int i;
			for(i=0; i<m_nodes.size(); i++) {
				n = (GraphNode) m_nodes.elementAt(i);
				r.x = (int)((nx+n.x)*scale); r.y = (int)((ny+n.y)*scale);
				if(r.contains(x,y)) {
					if(n.probs==null)
						return;
					
					int noOfPrntsOutcomes = 1;
					if(n.prnts!=null) {
						for(int j=0; j<n.prnts.length; j++) {
							GraphNode n2 = (GraphNode)m_nodes.elementAt(n.prnts[j]);
							noOfPrntsOutcomes *= n2.outcomes.length;
						}
						if(noOfPrntsOutcomes>511) {
							System.err.println("Too many outcomes of parents ("+noOfPrntsOutcomes+
							") can't display probabilities");
							return;
						}
					}
					
					GraphVisualizerTableModel tm = 
						new GraphVisualizerTableModel(n.probs, n.outcomes);
					
					JTable jTblProbs = new JTable(tm); //JTable(probabilities, (Object[])n.outcomes);
					
					JScrollPane js = new JScrollPane(jTblProbs);
					
					if(n.prnts!=null) {
						GridBagConstraints gbc = new GridBagConstraints();
						JPanel jPlRowHeader = new JPanel( new GridBagLayout() );
						
						//indices of the parent nodes in the Vector
						int [] idx = new int[n.prnts.length]; 
						//max length of values of each parent
						int [] lengths = new int[n.prnts.length];  
						
						//System.out.println("n.probs.length "+n.probs.length+
						//                   " should be "+noOfPrntsOutcomes);
						//System.out.println("n.probs[0].length "+n.probs[0].length+
						//                   " should be "+n.outcomes.length);
						//System.out.println("probabilities are: ");
						//for(int j=0; j<probabilities.length; j++) {
						//    for(int k=0; k<probabilities[j].length; k++)
						//	   System.out.print(probabilities[j][k]+" ");
						//     System.out.println("");
						//}
						
						//Adding labels for rows
						gbc.anchor = gbc.NORTHWEST;
						gbc.fill = gbc.HORIZONTAL;
						gbc.insets = new Insets(0,1,0,0);
						int addNum=0, temp=0;
						boolean dark=false;
						while(true){
							GraphNode n2;
							gbc.gridwidth = 1;
							for(int k=0; k<n.prnts.length; k++) {
								n2 = (GraphNode)m_nodes.elementAt(n.prnts[k]);
								JLabel lb = new JLabel(n2.outcomes[idx[k]]);
								lb.setFont( new Font("Dialog", Font.PLAIN, 12) );
								lb.setOpaque( true );
								lb.setBorder( BorderFactory.createEmptyBorder( 1,2,1,1 ) );
								lb.setHorizontalAlignment( JLabel.CENTER );
								if(dark) {
									lb.setBackground( lb.getBackground().darker() );
									lb.setForeground( Color.white );
								}
								else
									lb.setForeground( Color.black );
								
								temp = lb.getPreferredSize().width;
								//System.out.println("Preferred width "+temp+
								//                   " for "+n2.outcomes[idx[k]]);
								lb.setPreferredSize(
										new Dimension(temp, jTblProbs.getRowHeight())
								);
								if(lengths[k]<temp)
									lengths[k] = temp;
								temp=0;
								
								if(k==n.prnts.length-1) {
									gbc.gridwidth = gbc.REMAINDER;
									dark = (dark==true) ?  false:true;
								}
								jPlRowHeader.add(lb, gbc);
								addNum++;
							}
							
							for(int k=n.prnts.length-1; k>=0; k--) {
								n2 = (GraphNode) m_nodes.elementAt(n.prnts[k]);
								if(idx[k]==n2.outcomes.length-1 && k!=0) {
									idx[k]=0;
									continue;
								}
								else {
									idx[k]++;
									break;
								}
							}
							
							n2 = (GraphNode) m_nodes.elementAt(n.prnts[0]);
							if(idx[0]==n2.outcomes.length) {
								JLabel lb= (JLabel) jPlRowHeader.getComponent(addNum-1);
								jPlRowHeader.remove(addNum-1);
								lb.setPreferredSize( new Dimension(lb.getPreferredSize().width, 
										jTblProbs.getRowHeight()) );
								gbc.gridwidth = gbc.REMAINDER;
								gbc.weighty = 1;
								jPlRowHeader.add(lb, gbc);
								gbc.weighty=0;
								break;
							}
						}
						
						
						gbc.gridwidth = 1;
						//The following panel contains the names of the parents
						//and is displayed above the row names to identify
						//which value belongs to which parent
						JPanel jPlRowNames = new JPanel(new GridBagLayout());
						for(int j=0; j<n.prnts.length; j++) {
							JLabel lb2;
							JLabel lb1 = 
								new JLabel( ((GraphNode)m_nodes.elementAt(n.prnts[j])).lbl );
							lb1.setBorder( BorderFactory.createEmptyBorder( 1,2,1,1 ) );
							Dimension tempd = lb1.getPreferredSize();
							//System.out.println("lengths[j]: "+lengths[j]+
							//                   " tempd.width: "+tempd.width);
							if(tempd.width<lengths[j]) {
								lb1.setPreferredSize( new Dimension(lengths[j], tempd.height) );
								lb1.setHorizontalAlignment( JLabel.CENTER );
								lb1.setMinimumSize( new Dimension(lengths[j], tempd.height) );
							}
							else if(tempd.width>lengths[j]) {
								lb2 = (JLabel) jPlRowHeader.getComponent(j);
								lb2.setPreferredSize( new Dimension(tempd.width, 
										lb2.getPreferredSize().height) );
							}
							jPlRowNames.add(lb1, gbc);
							//System.out.println("After adding "+lb1.getPreferredSize());
						}
						js.setRowHeaderView(jPlRowHeader);
						js.setCorner( JScrollPane.UPPER_LEFT_CORNER, jPlRowNames );
						
						
					}
					
					
					JDialog jd = 
						new JDialog((Frame)GraphVisualizer.this.getTopLevelAncestor(),
								"Probability Distribution Table For "+n.lbl, true);
					jd.setSize(500, 400);
					//TODO agregado PATT para centrar las tablas de prob
					Dimension pantalla = Toolkit.getDefaultToolkit().getScreenSize();
					Dimension freim = jd.getSize();
					if (pantalla.width < freim.width){
						freim.width = pantalla.width;
					}
					if (pantalla.height < freim.height) {
						freim.height = pantalla.height;
					}
					jd.setLocation((pantalla.width-freim.width)/2,(pantalla.height-freim.height)/2);
					
					
//					jd.setLocation(GraphVisualizer.this.getLocation().x+
//					GraphVisualizer.this.getWidth()/2-250,
//					GraphVisualizer.this.getLocation().y+
//					GraphVisualizer.this.getHeight()/2-200 );
					
					jd.getContentPane().setLayout( new BorderLayout() );
					jd.getContentPane().add(js, BorderLayout.CENTER);
					jd.setVisible(true);
					
					return;
				}
			}
		}
		
	}
	
	
	/**
	 * private class for handling mouseMoved events
	 * to highlight nodes if the the mouse is moved on
	 * one
	 */
	private class GraphVisualizerMouseMotionListener extends MouseMotionAdapter {
		int x, y, nx, ny; Rectangle r;
		GraphNode lastNode;
		
		public void mouseMoved(MouseEvent me) {
			GraphNode n;
			Dimension d = m_gp.getPreferredSize();
			//System.out.println("Preferred Size: "+this.getPreferredSize()+
			//                   " Actual Size: "+this.getSize());
			x=y=nx=ny=0;
			
			if(d.width < m_gp.getWidth())
				nx = (int)((nx + m_gp.getWidth()/2 - d.width/2)/scale);
			if(d.height < m_gp.getHeight())
				ny = (int)((ny + m_gp.getHeight()/2 - d.height/2)/scale);
			
			r=new Rectangle(0, 0, 
					(int)(paddedNodeWidth*scale), (int)(nodeHeight*scale));
			x += me.getX(); y += me.getY();
			
			int i;
			for(i=0; i<m_nodes.size(); i++) {
				n = (GraphNode) m_nodes.elementAt(i);
				r.x = (int)((nx+n.x)*scale); r.y = (int)((ny+n.y)*scale);
				if(r.contains(x,y)) {
					if(n!=lastNode) {
						m_gp.highLight(n);
						if(lastNode!=null)
							m_gp.highLight(lastNode);
						lastNode = n; //lastIndex = i;
					}
					break;
				}
			}
			if(i==m_nodes.size()  && lastNode!=null) {
				m_gp.repaint();
				//m_gp.highLight(lastNode);
				lastNode=null;
			}
		}
	}
	
	/**
	 * Main method to load a text file with the
	 * description of a graph from the command
	 * line
	 */
	public static void main(String [] args) {
		JFrame jf = new JFrame("Graph Visualizer");
		GraphVisualizer g = new GraphVisualizer();
		
		try{
			if(args[0].endsWith(".xml")) {
				//StringBuffer sb = new StringBuffer();
				//FileReader infile = new FileReader(args[0]);
				//int i;
				//while( (i=infile.read())!=-1) {
				//    sb.append((char)i);
				//}
				//System.out.println(sb.toString());
				//g.readBIF(sb.toString() );
				g.readBIF( new FileInputStream(args[0]) );
			}
			else {
				//BufferedReader infile=new BufferedReader();
				g.readDOT(new FileReader(args[0])); //infile);
			}
		}
		catch(IOException ex) { ex.printStackTrace(); }
		catch(BIFFormatException bf) { bf.printStackTrace(); System.exit(-1); }
		
		jf.getContentPane().add(g);
		//RepaintManager.currentManager(jf.getRootPane()).setDoubleBufferingEnabled(false);
		jf.setDefaultCloseOperation( jf.EXIT_ON_CLOSE );
		jf.setSize(800,600);
		//jf.pack();
		jf.setVisible(true);
	}
	
	
	public GraphPanel getJPanelVisualize() {
		return m_gp;
	}


	public JScrollPane getScrollPane() {
		return m_js;
	}

}
